package com.future.orm.executor;

import com.future.orm.mapping.BoundSql;
import com.future.orm.mapping.MappedStatement;
import com.future.orm.mapping.ParameterMapping;
import com.future.orm.session.Configuration;

import javax.persistence.Column;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 简单执行器
 *
 * @author shiyong
 * 2020/5/26 15:32
 */
public class SimpleExecutor implements Executor {

    private Connection connection = null;

    @Override
    public int update(Configuration configuration, MappedStatement mappedStatement, Object[] param) throws SQLException, NoSuchFieldException, IllegalAccessException {
        // 获取数据库连接
        connection = configuration.getDataSource().getConnection();
        connection.setAutoCommit(configuration.isAutoCommit());

        // 解析参数，转换执行语句
        BoundSql boundSql = mappedStatement.getBoundSql();

        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSql());
        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();

        // 获取参数类型
        Class<?> parameterType = mappedStatement.getParameterType();

        parameterHandler(preparedStatement, parameterType, parameterMappingList, param);

        preparedStatement.execute();

        return preparedStatement.getUpdateCount();
    }

    @Override
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object[] param) throws Exception {
        // 获取数据库连接
        connection = configuration.getDataSource().getConnection();

        // 解析参数，转换执行语句
        BoundSql boundSql = mappedStatement.getBoundSql();

        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSql());
        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();

        // 获取参数类型
        Class<?> parameterType = mappedStatement.getParameterType();

        parameterHandler(preparedStatement, parameterType, parameterMappingList, param);

        // 执行查询
        ResultSet resultSet = preparedStatement.executeQuery();
        Class<?> resultType = mappedStatement.getResultType();

        return resultHandler(resultSet, resultType);
    }

    /**
     * 处理查询参数
     *
     * @param preparedStatement 预编译语句
     * @param parameterType 参数类型
     * @param parameterMappingList 参数映射列表
     * @param param 参数
     * @author shiyong
     * 2020/6/11 8:01
    */
    private void parameterHandler(PreparedStatement preparedStatement, Class<?> parameterType,
                                  List<ParameterMapping> parameterMappingList, Object[] param) throws SQLException, NoSuchFieldException, IllegalAccessException {

        if (parameterType == String.class) {
            for (int i = 0; i < parameterMappingList.size(); i++) {
                preparedStatement.setObject(i+1, param[i]);
            }
        } else {
            for (int i = 0; i < parameterMappingList.size(); i++) {
                String parameterName = parameterMappingList.get(i).getParameterName();

                // 通过名称获取对应的属性信息
                Field declaredField = parameterType.getDeclaredField(parameterName);
                declaredField.setAccessible(true);

                // 获取属性对应的值
                Object value = declaredField.get(param[0]);

                preparedStatement.setObject(i+1, value);
            }
        }
    }

    /**
     * 处理查询结果
     *
     * @param resultSet 查询结果
     * @param resultType 返回类型
     * @return java.util.List<E>
     * @author shiyong
     * 2020/6/9 9:53
    */
    private <E> List<E> resultHandler(ResultSet resultSet, Class<?> resultType) throws SQLException, InvocationTargetException, IllegalAccessException, IntrospectionException, NoSuchMethodException, InstantiationException {
        // 解析查询结果集，按指定类型返回
        ResultSetMetaData metaData = resultSet.getMetaData();
        int columnCount = metaData.getColumnCount();

        List<E> resultList = new ArrayList<>();

        // 默认按Map类型返回
        if (resultType == null) {
            while (resultSet.next()) {
                HashMap<String, Object> map = new HashMap<>();

                // 遍历数据结果列, 根据列名和返回类型的属性名称映射，将结果值写入返回对象中
                for (int i=1;i<=columnCount;i++) {
                    // 获取数据列别名
                    String columnLabel = metaData.getColumnLabel(i);
                    Object value = resultSet.getObject(columnLabel);

                    map.put(columnLabel, value);
                }

                resultList.add((E) map);
            }

            return resultList;
        }

        // 通过注解获取数据列对应的对象属性
        Map<String, String> columnFieldMapping = getColumnFieldMapping(resultType);

        while (resultSet.next()) {
            // 创建对象
            E o = (E) resultType.getDeclaredConstructor().newInstance();

            // 遍历数据结果列, 根据列名和返回类型的属性名称映射，将结果值写入返回对象中
            for (int i = 1; i <= columnCount; i++) {
                // 获取数据列别名
                String columnLabel = metaData.getColumnLabel(i);
                Object value = resultSet.getObject(columnLabel);

                if (value instanceof Timestamp) {
                    value = ((Timestamp) value).toLocalDateTime();
                }

                String fieldName = columnFieldMapping.get(columnLabel) == null ? columnLabel : columnFieldMapping.get(columnLabel);

                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(fieldName, resultType);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(o, value);
            }

            resultList.add(o);
        }

        return resultList;
    }

    private Map<String, String> getColumnFieldMapping(Class<?> clazz) {
        Map<String, String> map = new HashMap<>();
        Field[] fields = clazz.getDeclaredFields();

        for (Field field : fields) {
            if (field.isAnnotationPresent(Column.class)) {
                Column declaredAnnotation = field.getDeclaredAnnotation(Column.class);
                String column = declaredAnnotation.name();
                map.put(column, field.getName());
            }
        }

        return map;
    }

    @Override
    public void commit() throws SQLException {
        connection.commit();
    }

    @Override
    public void rollback() throws SQLException {
        connection.rollback();
    }

    @Override
    public void close() throws SQLException {
        connection.close();
    }
}
